#include "stdafx.h"

namespace classifier_test
{

weight_by_accuracy_ensemble_classifier::weight_by_accuracy_ensemble_classifier()
{

}

weight_by_accuracy_ensemble_classifier::weight_by_accuracy_ensemble_classifier(const weight_by_accuracy_ensemble_classifier& Val)
	: stream_classifier(Val)
{
	ChunkSize = Val.ChunkSize;
	ClassifierCount = Val.ClassifierCount;
}

smart_ptr<stream_classifier> weight_by_accuracy_ensemble_classifier::clone() const
{
	return make_smart_ptr(new weight_by_accuracy_ensemble_classifier(*this));
}

int weight_by_accuracy_ensemble_classifier::get_chunk_size() const
{
	return ChunkSize;
}

void weight_by_accuracy_ensemble_classifier::set_chunk_size(int SetChunkSize)
{
	ChunkSize = SetChunkSize;
}

int weight_by_accuracy_ensemble_classifier::get_classifier_count() const
{
	return ClassifierCount;
}

void weight_by_accuracy_ensemble_classifier::set_classifier_count(int SetClassifierCount)
{
	ClassifierCount = SetClassifierCount;
}

void weight_by_accuracy_ensemble_classifier::build(subarray<const stream_record* const> Data)
{
	stream_classifier::train(Data);
}

void weight_by_accuracy_ensemble_classifier::train(const stream_record& Record)
{
	assert((int)TrData.size() < ChunkSize);
	assert(Domain.valid(Record));
	TrData.push_back(smart_ptr<stream_record>(new stream_record(Record)));
	if ((int)TrData.size() < ChunkSize) return;

	smart_ptr<classifier> C = BaseClassifier->clone();
	C->train(make_subarray(TrData).convert<record*>());
	Classifiers[&*C] = C;

	weigh_classifiers();
}

discr_value weight_by_accuracy_ensemble_classifier::classify(const stream_record& Record) const
{
	array<double> Probs(ClassCount, 0.0);
	double TotalWeight = 0;
	for (int I = 0; I < (int)List.size(); I++) TotalWeight += List[I].first;
	array<double> P;
	for (int I = 0; I < (int)List.size(); I++)
	{
		TotalWeight -= List[I].first;
		List[I].second->estimate(Record, P);
		for (int J = 0; J < ClassCount; J++) Probs[J] += P[J] * List[I].first;
		assert(List[I].first > 0);

		int Class = max_element(Probs.begin(), Probs.end()) - Probs.begin();
		for (int J = 0; J < ClassCount; J++)
			if (J != Class && Probs[J] + TotalWeight > Probs[Class])
			{
				Class = -1;
				break;
			}
		if (Class >= 0) return Class;
	}
	if (List.empty() && !Freqs.empty()) Probs = Freqs;
	return max_element(Probs.begin(), Probs.end()) - Probs.begin();
}

void weight_by_accuracy_ensemble_classifier::estimate(const stream_record& Record, array<double>& Probs) const
{
	Probs.assign(ClassCount, 0.0);
	array<double> P;
	for (int I = 0; I < (int)List.size(); I++)
	{
		List[I].second->estimate(Record, P);
		for (int J = 0; J < ClassCount; J++) Probs[J] += P[J] * List[I].first;
		assert(List[I].first > 0);
	}
	if (List.empty() && !Freqs.empty()) Probs = Freqs;
	normalize_probabilities(Probs);
}

void weight_by_accuracy_ensemble_classifier::initialize_options(command_line_options& Options) const
{
	stream_classifier::initialize_options(Options);
	Options.add("chunk-size").add_name("cz").set_argument("<count>").set_info("# of records in a chunk, default 100");
	Options.add("classifier-count").add_name("cc").set_argument("<count>").set_info("# of recent base classifiers/windows of data to hold, default 20");
}

void weight_by_accuracy_ensemble_classifier::apply_arguments(const command_line_arguments& Args)
{
	stream_classifier::apply_arguments(Args);
	ChunkSize = to_int(Args["cz"].get_or_default("100"));
	ClassifierCount = to_int(Args["cc"].get_or_default("20"));
}

void weight_by_accuracy_ensemble_classifier::compute_freqs()
{
	Freqs.assign(ClassCount, 0.0);
	for (int I = 0; I < (int)TrData.size(); I++)
		Freqs[TrData[I]->back().discr]++;
	for (int I = 0; I < ClassCount; I++) Freqs[I] /= TrData.size();
}

double weight_by_accuracy_ensemble_classifier::error_rate_threshold_of() const
{
	double Result = 0;
	for (int I = 0; I < ClassCount; I++)
		Result += Freqs[I] * (1 - Freqs[I]) * (1 - Freqs[I]);
	return Result;
}

double weight_by_accuracy_ensemble_classifier::error_rate_of(classifier* Classifier) const
{
	double Result = 0;
	array<double> P;
	for (int I = 0; I < (int)TrData.size(); I++)
	{
		int Label = TrData[I]->back().discr;
		Classifier->estimate(*TrData[I], P);
		Result += (1 - P[Label]) * (1 - P[Label]);
	}
	return Result / TrData.size();
}

void weight_by_accuracy_ensemble_classifier::weigh_classifiers()
{
	List.clear();
	compute_freqs();
	double Threshold;
	Threshold = error_rate_threshold_of();
	for (map<classifier*, smart_ptr<classifier> >::iterator It = Classifiers.begin(); It != Classifiers.end(); ++It)
	{
		double Weight;
		Weight = Threshold - error_rate_of(It->first);
		List.push_back(make_pair(Weight, It->first));
	}
	TrData.clear();
	sort(List.begin(), List.end(), greater<pair<double, classifier*> >());
	while ((int)List.size() > ClassifierCount || !List.empty() && List.back().first <= 0)
	{
		Classifiers.erase(Classifiers.find(List.back().second));
		List.pop_back();
	}
}

}

